import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Optional,
  Output,
  TemplateRef,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { HttpClient, HttpEvent, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http';
import {
  BehaviorSubject,
  Observable,
  of,
  Subject,
  Subscription,
  throwError,
  from,
  isObservable,
  onErrorResumeNext,
} from 'rxjs';
import { catchError, filter, last, mergeMap, switchMap, take, takeUntil, tap } from 'rxjs/operators';
import {
  assign as _assign,
  filter as _filter,
  forEach as _forEach,
  identity as _identity,
  map as _map,
  pickBy as _pickBy,
  remove as _remove,
  uniqBy as _uniqBy,
} from 'lodash';
import {
  fileToObject,
  formatSize,
  generateId,
  I18NService,
  InputBoolean,
  InputNumber,
  LvSizeLD,
  TypeUtils,
} from '../core/api';
import { MessageService } from '../message/api';
import { LvConfig } from '../core/config';
import {
  UploadContentModeEnum,
  UploadFile,
  UploadFileStatusEnum,
  UploadFilter,
  UploadHttpRequestOptions,
  UploadObject,
  UploadStatusEnum,
  UploadType,
  UploadUnitEnum,
} from './interface';

const HELP_TEXT_MAP: { lvFileLimit: string; lvMaxFileSize: string; lvAllowFileSuffix: string } = {
  lvFileLimit: 'invalidFileLimitMessageDetail',
  lvMaxFileSize: 'invalidFileSizeMessageDetail',
  lvAllowFileSuffix: 'invalidFileTypeMessageDetail',
};

@Component({
  selector: 'lv-upload',
  templateUrl: './upload.component.html',
  styles: [],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    class: 'lv-fileupload',
    '[class.lv-fileupload-disabled]': 'lvDisabled',
    '[class.lv-fileupload-size-large]': 'lvSize === "large" && lvType === "select" && !lvContentTemplate',
  },
})
export class UploadComponent implements OnInit, OnDestroy {
  @Input() lvUrl: string; // 上传地址
  @Input() lvPlaceholder: string; // 占位符
  @Input() lvAccept: string = '*'; // 接受的文件类型, 默认全部
  @Input() lvName: string = 'file'; // 用于标识后端文件的请求参数的名称
  @Input() lvMethod: string = 'post'; // 上传方式
  @Input() lvData: UploadObject = {}; // 上传所需参数
  @Input() lvAllowFileSuffix: string; // 允许上传的文件后缀，在绕过accept可增加使用;以,分割
  @Input() lvFiles: UploadFile[] = []; // 文件列表
  @Input() @InputBoolean() lvDisabled; // 是否禁用
  @Input() @InputBoolean() lvDragable; // 待实现: 是否可拖拽上传，该属性未使用，后续删除
  // 输入输出属性
  @Input() lvSize: LvSizeLD = 'default'; // 输入框的默认大小
  @Input() @InputBoolean() lvAutoUpload; // 是否自动上传
  @Input() lvHeaders: UploadObject = {}; // 设置请求头
  @Input() lvType: UploadType = 'select'; // 上传类型：选择、拖拽，默认选择
  @Input() lvFilters: UploadFilter[] = []; // 自定义过滤规则
  @Input() @InputBoolean() lvWithCredentials; // 上传请求时是否携带cookie
  @Input() lvCustomHttpRequest: UploadObject; // 自定义设置上传请求的参数，独立单项配置优先级高于当前配置
  @Input() lvUploadErrorMessage: string = ''; // 上传错误信息，多用于表单的错误校验
  @Input() lvExtra: string | TemplateRef<any>; // 是否显示额外的信息，位置在所有图标之前
  @Input() @InputNumber() lvFileLimit: number = 0; // 最大可上传文件个数，0表示不限
  @Input() @InputNumber() lvMaxFileSize: number = 0; // 允许上传单个文件最大大小，0表示不限
  @Input() @InputBoolean() lvMultiple: boolean = false; // 是否支持多文件上传
  @Input() @InputBoolean() lvShowCancel: boolean = true; // 是否支持取消上传按钮
  @Input() @InputBoolean() lvShowUpload: boolean = true; // 是否显示上传按钮
  @Input() @InputBoolean() lvShowRemove: boolean = true; // 是否显示删除按钮
  @Input() @InputNumber() lvConcurrentNumber: number = 0; // 限制单次上传最多并发数，0表示不限
  @Input() @InputBoolean() lvShowReupload: boolean = true; // 是否支持单个重试按钮
  @Input() lvContentTemplate: TemplateRef<{ $implicit: any }>; // 自定义上传面板
  @Input() @InputBoolean() lvShowBulkReupload: boolean = true; // 待实现 功能暂未实现 是否支持批量重试按钮
  @Input() lvMaxFileUnit: UploadUnitEnum = UploadUnitEnum.BYTE; // 允许上传单个文件最大大小的大单位。默认byte
  @Output() lvChange: EventEmitter<any> = new EventEmitter<any>(); // 选择文件、上传、成功、失败变化的回调
  @Input() lvHelpText: boolean | string | TemplateRef<any> = false; // 自定义帮助文本
  @ViewChild('fileInput', { static: false }) _fileInput: ElementRef;
  @Input() lvCustomUpload: (options: UploadHttpRequestOptions) => Observable<any>; // 自定义上传方法

  @Output() lvFilesChange: EventEmitter<UploadFile[]> = new EventEmitter<UploadFile[]>(); // files列表变更回调
  @Input() lvBeforeRemove: (event: MouseEvent, file: UploadFile) => boolean | Observable<boolean>; // 删除文件之前的钩子，若返回false停止删除
  @Input() lvBeforeUpload: (file: UploadFile, fileList: UploadFile[]) => boolean | void | Observable<boolean>; // 上传文件之前的钩子，若返回false停止上传

  @Input() lvBeforeSuccess: (response: HttpResponse<any>, file: UploadFile) => boolean | void | Observable<boolean>; // 文件上传成功之前的钩子，若返回false则上传失败
  mouseListener;
  TypeUtils = TypeUtils;
  isDragIn: boolean = false; // 拖拽文件是否在拖拽框内部
  fileLimitTip: string = '';
  typeUtils: TypeUtils = TypeUtils;
  fileLimitTipOriginEle: EventTarget;
  uploadStatusEnum = UploadStatusEnum;
  // 外部可访问变量
  uploadContentMode = UploadContentModeEnum;
  uploadFileStatusEnum = UploadFileStatusEnum;
  mousePosition: { x: number; y: number } = { x: 0, y: 0 };
  public assetsPrefixPath: string = LvConfig.assetsPrefixPath;
  uploadBth: any = this._i18n.get(this.lvMultiple ? 'uploadFiles' : 'uploadFile');
  uploadProcess$: BehaviorSubject<UploadStatusEnum> = new BehaviorSubject<UploadStatusEnum>(UploadStatusEnum.INIT); // 整个上传的进度

  private _filesMap = {}; // 保存每个文件和对应的上传流映射关系
  // 内部变量
  private _destroy$ = new Subject<void>();
  private _refresh$ = new Subject<void>();
  private _uploadSub = Subscription.EMPTY;

  /**
   * 1. Select
   * 2. Valid
   * 3. beforeUpload
   * 4. Upload
   * 5. beforeSuccess
   * 6. Success
   * 7. Error
   */
  constructor(
    private _i18n: I18NService,
    private _messageService: MessageService,
    @Optional() private _http: HttpClient,
    private _cdr: ChangeDetectorRef,
    private sanitizer: DomSanitizer,
  ) {
    if (!_http) {
      throw new Error("Not found 'HttpClient', You have to import 'HttpClientModule' in your root module.");
    }
    this.lvPlaceholder = this._i18n.get(this.lvMultiple ? 'selectFiles' : 'selectFile');
  }

  // 是否在上传中
  get isProgress(): boolean {
    return this.uploadProcess$.value === UploadStatusEnum.PROGRESS;
  }

  get fileLength(): number {
    return (this.lvFiles instanceof Array ? this.lvFiles : []).length;
  }

  // 待实现
  get uploadErrorLength(): any {
    return _filter(this.lvFiles, (item): boolean => item.status === UploadFileStatusEnum.ERROR).length;
  }

  // 待实现
  get uploadingIndex(): number {
    const readyLength: any = _filter(
      this.lvFiles,
      (item): boolean => item.status === UploadFileStatusEnum.READY,
    ).length;
    return this.fileLength - readyLength;
  }

  get contentMode(): UploadContentModeEnum {
    // 上传面板模式，默认input+btn
    if (this.lvContentTemplate) {
      return UploadContentModeEnum.CUSTOM;
    } else if (this.lvType === 'drag') {
      return UploadContentModeEnum.DRAG;
    } else if (this.lvType === 'picture') {
      return UploadContentModeEnum.PICTURE;
    }
    return UploadContentModeEnum.SELECT;
  }

  // 获取帮助文本
  get helpText(): string | TemplateRef<any> {
    if (TypeUtils.isRealString(this.lvHelpText)) {
      return this.lvHelpText as string;
    }
    if (TypeUtils.isTemplateRef(this.lvHelpText)) {
      return this.lvHelpText as TemplateRef<any>;
    }
    let helpText: string = '';
    if (this.lvFileLimit && this.lvMultiple) {
      helpText += this._i18n.get(HELP_TEXT_MAP.lvFileLimit, [this.lvFileLimit]);
    }
    if (this.lvMaxFileSize) {
      const fileSize: string = `${this.lvMaxFileSize}${this.lvMaxFileUnit}`;
      helpText += this._i18n.get(HELP_TEXT_MAP.lvMaxFileSize, [fileSize]);
    }
    if (this.lvAllowFileSuffix) {
      helpText += this._i18n.get(HELP_TEXT_MAP.lvAllowFileSuffix, [this.lvAllowFileSuffix]);
    }
    return helpText;
  }

  _refresh(): void {
    this._refresh$.next();
  }

  // 批量重新上传文件
  reUploadFiles(): void {
    this.uploadFiles();
  }

  _getPositiveInteger(n: number = 0): number {
    return Math.max(n, 0);
  }

  // 生成文件唯一id
  _setFilekey(): string {
    return `lv-file${new Date().getTime()}-${generateId()}`;
  }

  setUploadStatus(value: UploadStatusEnum): void {
    this.uploadProcess$.next(value);
  }

  // 重新上传某个文件
  reUploadFile(e: MouseEvent, file: UploadFile): void {
    e.stopPropagation();
    this.uploadFiles([file]);
  }

  ngOnInit(): void {
    this._refresh$.pipe(takeUntil(this._destroy$)).subscribe((): void => this._cdr.markForCheck());
  }

  _throwError(error: any): Observable<never> {
    return throwError(typeof error === 'string' ? { message: error } : error);
  }

  _onSend(file: UploadFile): void {
    file.status = UploadFileStatusEnum.PROGRESS;
    this._emitChange(file, file.status);
  }

  /**
   * 1 select 点击选择文件
   *
   */
  selectFile(e?: MouseEvent): void {
    if (e) {
      this.fileLimitTipOriginEle = e.currentTarget;
    }
    this._openFileSelect();
  }

  _onFileDragover($event: DragEvent): void {
    $event.preventDefault();
    if (!this.lvDisabled) {
      this.isDragIn = true;
    }
  }

  _onFileDragleave($event: DragEvent): void {
    $event.preventDefault();
    if (!this.lvDisabled) {
      this.isDragIn = false;
    }
  }

  _filesChange(files: UploadFile[], event: UploadFileStatusEnum): void {
    this.lvFilesChange.emit(files);
    this._emitChange(files, event);
  }

  ngOnDestroy(): void {
    this._destroy$.next();
    this._destroy$.complete();
    document.removeEventListener('mousemove', this.mouseListener);
  }

  /**
   *
   * 上传处理事件
   * onProgress 上传中处理事件
   * onSuccess 上传成功处理事件
   * onError 上传失败处理事件
   */
  _setFilesStatus(files: UploadFile[], status: UploadFileStatusEnum): void {
    _forEach(files, (item): void => {
      item.status = status;
    });
  }

  /**
   * files列表过滤上传成功的文件
   */
  _getReadyFiles(files?: UploadFile[]): any {
    return _filter(files || this.lvFiles, (item): boolean => item.status !== UploadFileStatusEnum.SUCCESS);
  }

  // 打开文件选择器
  _openFileSelect(): void {
    if (this.fileLimitTip) {
      return;
    }
    if (this.lvDisabled) {
      return;
    }
    this._fileInput.nativeElement.click();
  }

  // 获取自定义请求头配置
  _getCustomHttpRequestOptions(file: UploadFile, data: { [key: string]: any }): any {
    if (data instanceof Function) {
      return data(file);
    }
    return data || {};
  }

  _onError(error: any, file: UploadFile): void {
    file.status = UploadFileStatusEnum.ERROR;
    file.error = error;
    this._emitChange(file, file.status);
    this._refresh();
  }

  _onProgress(progress: number, file: UploadFile): void {
    file.status = UploadFileStatusEnum.PROGRESS;
    file.progress = progress;
    this._emitChange(file, file.status);
    this._refresh();
  }

  _emitChange(file: UploadFile[] | UploadFile, status: UploadFileStatusEnum): void {
    this.lvChange.emit({
      activeFiles: file, // 当前处理的文件列表
      files: this.lvFiles, // 当前文件列表
      action: status, // 行为
    });
  }

  ngAfterViewInit(): void {
    this.mouseListener = (e: MouseEvent): void => {
      this.mousePosition.x = e.pageX;
      this.mousePosition.y = e.pageY;
    };
    document.addEventListener('mousemove', this.mouseListener);
  }

  // 文件拖入
  _onFileDrop($event: DragEvent): void {
    $event.preventDefault();
    if (this.lvDisabled) {
      return;
    }
    const { files }: DataTransfer = $event.dataTransfer;
    this.isDragIn = false;
    this.onFileChange(files);
  }

  _isDirtyFiles(files: UploadFile[]): boolean {
    const str: string = '|#|';
    const source: any = _map(files, 'key').sort().join(str);
    const target: any = _map(this.lvFiles, 'key').sort().join(str);
    return source === target;
  }

  // 取消上传
  cancelFile(): void {
    this._uploadSub.unsubscribe();
    _forEach(this.lvFiles, (item): UploadFileStatusEnum.ERROR => (item.status = UploadFileStatusEnum.ERROR));
    this.setUploadStatus(UploadStatusEnum.ERROR);
    this._refresh();
  }

  // 切换文件并校验
  fileChange(e: Event): void {
    const target: HTMLInputElement = e.target as HTMLInputElement;
    const { files }: HTMLInputElement = target;
    if (!files || !files.length) {
      return;
    }
    this.onFileChange(files);
    target.value = '';
  }

  // 校验文件后缀
  _validSuffix(filename: string, suffix: string[]): boolean {
    return suffix.some((item): boolean => {
      if (!String.prototype.endsWith) {
        return filename.substring(filename.length - item.length, filename.length) === item;
      }
      return filename.endsWith(item);
    });
  }

  _beforeSuccessHook(event: any, file: UploadFile): any {
    return this._getBeforeSuccessStream(event, file).pipe(
      switchMap((res): Observable<null> | Observable<never> => {
        if (!res) {
          return this._throwError(event);
        }
        return of(event);
      }),
    );
  }

  _isInFileLimitOrigin(): boolean {
    const rect: DOMRect = (this.fileLimitTipOriginEle as HTMLElement).getBoundingClientRect();
    return (
      (this.mousePosition.x >= rect.left && this.mousePosition.x <= rect.right) ||
      (this.mousePosition.y >= rect.top && this.mousePosition.x <= rect.bottom)
    );
  }

  _onSuccess(response: any, file: UploadFile): void {
    file.status = UploadFileStatusEnum.SUCCESS;
    file.response = response;
    file.url =
      response?.body?.url || this.sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(file.originFile));
    this._emitChange(file, file.status);
    this._refresh();
  }

  // 获取头
  _getHeaders(file: UploadFile): any {
    const readyHeades: { [p: string]: any } | ((file: UploadFile) => { [p: string]: any }) = this.lvHeaders;
    if (!readyHeades) {
      return null;
    } else if (readyHeades instanceof Function) {
      return new HttpHeaders(readyHeades(file));
    }
    return new HttpHeaders(readyHeades);
  }

  /**
   * 上传文件：before + 上传
   * @param file: UploadFile
   */
  _uploadFile(file: UploadFile): Observable<any> {
    const before$: Observable<boolean> = this._beforeUploadHook(file);
    this._filesMap[file.key] = new Subject<void>();
    return before$.pipe(
      switchMap((): Observable<any> => this._upload(file)),
      takeUntil(this._filesMap[file.key]),
      takeUntil(this._destroy$),
    );
  }

  // 是否可以上传: 准备中或失败
  isUpload(files?: UploadFile[]): boolean {
    const status = this.uploadProcess$.value;
    const validStatus = [UploadStatusEnum.READY, UploadStatusEnum.ERROR];
    const validFile: boolean = !!_filter(files || this.lvFiles, (item): boolean => validStatus.includes(item.status))
      .length;
    return validStatus.includes(status) && validFile;
  }

  /**
   * 2 valid 校验上传的文件列表 内置+自定义
   * 内置：文件大小、文件后缀、文件个数上限
   */
  getVaildFilterList(): UploadFilter[] {
    const filters: UploadFilter[] = [this.getSizeFilter(), this.getSuffixFilter(), this.getLimitFilter()];

    if (!this.lvFilters) {
      this.lvFilters = [];
    }
    this.lvFilters.push(...filters);
    // 多次操作完成后会导致filters重复，filters变量需去重
    this.lvFilters = _uniqBy(this.lvFilters, 'name');
    return this.lvFilters;
  }

  _beforeUploadHook(file: UploadFile): Observable<boolean> {
    return this._getBeforeUploadStream(file).pipe(
      filter((res): boolean => {
        // beforeUpload 取消上传
        if (!res) {
          file.status = UploadFileStatusEnum.ERROR;
          this._emitChange(file, file.status);
          this.removeFile(null, file);
        }
        return res;
      }),
    );
  }

  // 获取formdata
  _getFormData(file: UploadFile): FormData {
    const formdata = new FormData();
    const name = this.lvName;
    formdata.append(name, file.originFile, file.name);
    let extendData = this.lvData;

    if (extendData instanceof Function) {
      extendData = extendData(file);
    }

    _forEach(extendData, (item, key): void => {
      formdata.append(key, item);
    });

    return formdata;
  }

  // 获取httprequest
  getHttpRequest(options: UploadHttpRequestOptions): HttpRequest<FormData> {
    const { params, responseType } = options;
    const data = _pickBy({ params, responseType }, _identity);
    return new HttpRequest(options.method, options.url, options.formData, {
      reportProgress: true, // 监听并返回xhr进度事件
      headers: options.httpHeaders,
      withCredentials: options.withCredentials,
      ...data,
    });
  }

  // 根据传入的单位确定maxSize值
  _getMaxSizeInteger(n: number = 0): number {
    switch (this.lvMaxFileUnit) {
      case UploadUnitEnum.BYTE:
        return this._getPositiveInteger(n);
      case UploadUnitEnum.KB:
        return this._getPositiveInteger(n * 1024);
      case UploadUnitEnum.MB:
        return this._getPositiveInteger(n * 1024 * 1024);
      case UploadUnitEnum.GB:
        return this._getPositiveInteger(n * 1024 * 1024 * 1024);
      default:
        return null;
    }
  }

  // 处理上传中事件
  _setProgressEvent(event: HttpEvent<any>, options: UploadHttpRequestOptions): void {
    const { type } = event;
    const { file } = options;
    if (type === HttpEventType.Sent) {
      options.onSend(file);
    } else if (type === HttpEventType.UploadProgress) {
      // tslint:disable-next-line: no-string-literal
      const progress: number = Math.round((100 * event['loaded']) / event['total']);
      options.onProgress(progress, file);
    } else {
      return;
    }
  }

  _getBeforeRemoveStream(e: MouseEvent, file: UploadFile): Observable<boolean> {
    const beforeRemoveFn: (event: MouseEvent, file: UploadFile) => boolean | Observable<boolean> = this.lvBeforeRemove;
    let output$: Observable<boolean> = of(true);
    if (!beforeRemoveFn) {
      return output$;
    }
    const result: boolean | Observable<boolean> = beforeRemoveFn(e, file);
    if (isObservable(result)) {
      output$ = result.pipe(take(1)); // 只接受一次结果
    } else if (typeof result === 'boolean') {
      output$ = of(result);
    }
    return output$;
  }

  // 获取验证文件正确性的流
  _getVaildFilesStream(files: UploadFile[]): Observable<UploadFile[]> {
    const filters: UploadFilter[] = this.getVaildFilterList();
    let vaildFiles$: Observable<UploadFile[]> = of(files);
    _forEach(filters, (item: UploadFilter): void => {
      vaildFiles$ = vaildFiles$.pipe(
        switchMap((res: UploadFile[]): Observable<UploadFile[]> => {
          const filesSub: UploadFile[] | Observable<UploadFile[]> = item.filterFn(res);
          return isObservable(filesSub) ? filesSub : of(filesSub);
        }),
      );
    });
    return vaildFiles$;
  }

  _getUploadFilesStream2(files?: UploadFile[]): Observable<null> {
    const filters: UploadFilter[] = this.getVaildFilterList();
    let vaildFiles$ = of(this._getReadyFiles(files)); // 获取未上传完成过的文件
    _forEach(filters, (item: UploadFilter): void => {
      vaildFiles$ = vaildFiles$.pipe(
        switchMap((res: UploadFile[]): Observable<UploadFile[]> => {
          const filesSub: UploadFile[] | Observable<UploadFile[]> = item.filterFn(res);
          return isObservable(filesSub) ? filesSub : of(filesSub);
        }),
      );
    });
    return vaildFiles$;
  }

  _getBeforeUploadStream(file: UploadFile): Observable<boolean> {
    const beforeUploadFn: (file: UploadFile, fileList: UploadFile[]) => boolean | void | Observable<boolean> =
      this.lvBeforeUpload;
    let output$: Observable<boolean> = of(true);
    if (!beforeUploadFn) {
      return output$;
    }
    const result: boolean | void | Observable<boolean> = beforeUploadFn(file, this.lvFiles);
    if (isObservable(result)) {
      output$ = result.pipe(take(1)); // 只接受一次结果
    } else if (typeof result === 'boolean') {
      output$ = of(result);
    }
    return output$;
  }

  _getBeforeSuccessStream(response: HttpResponse<any>, file: UploadFile): Observable<boolean> {
    const beforeSuccessFn: (response: HttpResponse<any>, file: UploadFile) => boolean | void | Observable<boolean> =
      this.lvBeforeSuccess;
    let output$: Observable<boolean> = of(true);
    if (!beforeSuccessFn) {
      return output$;
    }
    const result: boolean | void | Observable<boolean> = beforeSuccessFn(response, file);
    if (isObservable(result)) {
      output$ = result.pipe(take(1)); // 只接受一次结果
    } else if (typeof result === 'boolean') {
      output$ = of(result);
    }
    return output$;
  }

  getSuffixFilter(): UploadFilter {
    return {
      name: 'lv-inner-suffix',
      filterFn: (files: UploadFile[]): any => {
        if (!this.lvAllowFileSuffix) {
          return files;
        }

        const suffixs: string[] = (this.lvAllowFileSuffix || '').split(',');
        const filterFiles: any = _filter(files, (item): boolean => this._validSuffix(item.name, suffixs));

        if (filterFiles.length !== files.length) {
          this._messageService.error(
            this._i18n.get('invalidFileTypeMessageDetail').replace('{0}', this.lvAllowFileSuffix),
          );
        }
        return filterFiles;
      },
    };
  }

  // 单独抽离文件组装、校验方法
  onFileChange(files): void {
    const filelist = Array.from(files).map((item: any): any =>
      _assign({}, fileToObject(item), {
        status: UploadFileStatusEnum.SELECT,
        key: this._setFilekey(),
        fileSize: formatSize(item.size),
        originFile: item,
      }),
    );

    this.setUploadStatus(UploadStatusEnum.INIT);
    // 2 校验并判断是否上传
    if (!this.lvMultiple) {
      this.lvFiles = [];
    } else {
      !(this.lvFiles instanceof Array) && (this.lvFiles = []);
    }
    this._emitChange(filelist, UploadFileStatusEnum.SELECT);
    const allFiles: any[] = [...this.lvFiles, ...filelist];
    this.validFiles(this.lvAutoUpload, allFiles);
  }

  _getUploadStatus(): UploadStatusEnum {
    const files: UploadFile[] = this.lvFiles;
    if (!files || !files.length) {
      return UploadStatusEnum.INIT;
    }
    const allStatus: any = _map(files, 'status');
    if (allStatus.every((o): boolean => o === UploadStatusEnum.SUCCESS)) {
      return UploadStatusEnum.SUCCESS;
    } else if (allStatus.every((o): boolean => o === UploadStatusEnum.ERROR)) {
      return UploadStatusEnum.ERROR;
    } else if (allStatus.some((o): boolean => o === UploadStatusEnum.PROGRESS)) {
      return UploadStatusEnum.PROGRESS;
    } else if (allStatus.some((o): boolean => o === UploadStatusEnum.READY)) {
      return UploadStatusEnum.READY;
    } else {
      return UploadStatusEnum.INIT;
    }
  }

  // 删除文件
  async removeFile(e: MouseEvent, file: UploadFile): Promise<void> {
    e?.stopPropagation();

    if (this.lvDisabled) {
      return;
    }
    if (e && this.lvBeforeRemove) {
      const res: boolean = await this._getBeforeRemoveStream(e, file).toPromise();
      if (!res) {
        return;
      }
    }

    if (this._filesMap[file.key]) {
      this._filesMap[file.key].next();
      this._filesMap[file.key] = null;
    }
    const removeFile: any = _remove(this.lvFiles, (item): boolean => item.key === file.key);
    removeFile.status = UploadFileStatusEnum.REMOVE;
    this.fileLimitTip = '';
    this.lvFilesChange.emit(this.lvFiles);
    this._emitChange(removeFile, removeFile.status);
    this.setUploadStatus(this._getUploadStatus());
    this._refresh();
  }

  // 上传文件
  uploadFiles(files?: UploadFile[]): void {
    if (this.lvDisabled) {
      return;
    }

    const readyFiles: UploadFile[] = this._getReadyFiles(files);
    if (!readyFiles.length) {
      return;
    }

    this._setFilesStatus(readyFiles, UploadFileStatusEnum.READY);

    let concurrentNum: number = this._getPositiveInteger(this.lvConcurrentNumber);
    concurrentNum = concurrentNum ? concurrentNum : readyFiles.length;

    this._uploadSub = from(readyFiles)
      .pipe(
        tap((): void => {
          this._filesMap = {};
        }),
        tap((): void => this.setUploadStatus(UploadStatusEnum.PROGRESS)),
        mergeMap((res): Observable<any> => this._uploadFile(res), concurrentNum),
        takeUntil(this._destroy$),
      )
      .subscribe((): void => {
        this.setUploadStatus(UploadStatusEnum.SUCCESS);
      });
  }

  _getUploadHttpRequestOptions(uploadFile: UploadFile, customOptions: UploadObject): UploadHttpRequestOptions {
    const custom: any = this._getCustomHttpRequestOptions(uploadFile, customOptions);
    return {
      ...custom,
      url: this.lvUrl,
      method: this.lvMethod,
      file: uploadFile,
      originFile: uploadFile.originFile,
      httpHeaders: this._getHeaders(uploadFile),
      headers: this.lvHeaders,
      formData: this._getFormData(uploadFile),
      data: this.lvData,
      withCredentials: this.lvWithCredentials,
      onSend: (file: UploadFile): void => {
        this._onSend(file);
      },
      onProgress: (progress: number, file: UploadFile): void => {
        this._onProgress(progress, file);
      },
      onSuccess: (response: any, file: UploadFile): void => {
        this._onSuccess(response, file);
      },
      onError: (error: any, file: UploadFile): void => {
        this._onError(error, file);
      },
    };
  }

  /**
   * 下发上传: 默认上传、自定义上传
   */
  _upload(file: UploadFile): any {
    const options = this._getUploadHttpRequestOptions(file, this.lvCustomHttpRequest);
    const customUpload = this.lvCustomUpload;
    // 自定义
    if (customUpload) {
      return customUpload(options).pipe(last(), takeUntil(this._destroy$));
    }

    // 默认
    return onErrorResumeNext(
      this._http.request(this.getHttpRequest(options)).pipe(
        tap((event): void => this._setProgressEvent(event, options)),
        last(), // 返回最后一次结果
        // 处理请求完成前的钩子函数
        switchMap((event: HttpResponse<any>): Observable<never> => this._beforeSuccessHook(event, options.file)),
        tap((res): void => {
          options.onSuccess(res, options.file);
        }),
        catchError((error): Observable<never> => {
          options.onError(error, options.file);
          this.setUploadStatus(UploadStatusEnum.ERROR);
          return this._throwError(error);
        }),
        takeUntil(this._destroy$),
      ),
    );
  }

  /**
   * 2 valid 校验上传的文件列表 内置+自定义
   * (选择文件+以前文件)->(过滤)->有效文件
   * ---- 是否自动
   * 3
   * 4
   * 5
   * 6
   * 7
   */
  validFiles(auto: boolean, files: UploadFile[]): void {
    if (this.lvDisabled) {
      return;
    }
    const files$: Observable<UploadFile[]> = this._getVaildFilesStream(files);
    files$.pipe(takeUntil(this._destroy$)).subscribe((res): void => {
      if (!res?.length) {
        this.lvFiles = [];
        this._filesChange(this.lvFiles, UploadFileStatusEnum.SELECT);
        return;
      }

      // 判断两次的数据是否相同（多选场景不再清空已有数据）
      const isDirty: boolean = this._isDirtyFiles(res);

      if (!this.lvFiles || !this.lvMultiple) {
        this.lvFiles = [];
      }
      _forEach(res, (item): void => {
        if (item.status === UploadFileStatusEnum.SELECT) {
          item.status = UploadFileStatusEnum.READY;
        }
      });
      this.lvFiles = res;
      this.setUploadStatus(UploadStatusEnum.READY);
      this._filesChange(this.lvFiles, UploadFileStatusEnum.READY);

      // 两次的数据相同，则不再执行上传操作（之前已执行过）
      if (auto && !isDirty) {
        this.uploadFiles();
      }
    });
  }

  getLimitFilter(): UploadFilter {
    return {
      name: 'lv-inner-limit',
      filterFn: (files: UploadFile[]): UploadFile[] => {
        if (!this.lvMultiple) {
          return files;
        }
        const maxLimit: number = this._getPositiveInteger(this.lvFileLimit);
        if (!maxLimit) {
          return files;
        }
        const filterFiles: UploadFile[] = files.slice(0, maxLimit);

        if (filterFiles.length === maxLimit) {
          this.fileLimitTip = this._i18n.get('uploadTriggerFileLimitTips').replace('{0}', maxLimit);
          if (this.fileLimitTipOriginEle && this._isInFileLimitOrigin()) {
            this.fileLimitTipOriginEle.dispatchEvent(new Event('mouseenter'));
          }
        } else {
          this.fileLimitTip = '';
        }

        if (filterFiles.length !== files.length) {
          this._messageService.error(this._i18n.get('invalidFileLimitMessageDetail').replace('{0}', maxLimit));
        }
        return filterFiles;
      },
    };
  }

  getSizeFilter(): UploadFilter {
    return {
      name: 'lv-inner-size',
      filterFn: (files: UploadFile[]): any => {
        const validMinSizeFiles: any = _filter(files, (item): boolean => item.size > 0);
        // 先判断是否有空文件，如果未配置lvMaxFileSize不影响空文件判断
        if (validMinSizeFiles.length !== files.length) {
          this._messageService.error(this._i18n.get('invalidFileSizeZeroMessage'));
        }

        const maxSize: number = this._getMaxSizeInteger(this.lvMaxFileSize);
        // 判断文件大小是否超过最大限制，无最大限制时，直接返回文件
        if (!maxSize) {
          return validMinSizeFiles;
        }

        const validMaxSizeFiles: any = _filter(validMinSizeFiles, (item): boolean => item.size <= maxSize);
        if (validMaxSizeFiles.length !== validMinSizeFiles.length) {
          // 提示语添加单位
          this._messageService.error(
            this._i18n.get('invalidFileSizeMessageDetail').replace('{0}', `${this.lvMaxFileSize}${this.lvMaxFileUnit}`),
          );
        }

        return validMaxSizeFiles;
      },
    };
  }
}
